﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {
    /// <summary>
    /// 見え方を定義するアピアランス クラス
    /// </summary>
    /// <remarks>
    /// ポリゴンを塗りつぶす時のシェーダー、マテリアル､テクスチャーなどを総称してアピアランスと呼びます｡
    /// アピアランスは"物体の見え方"を決定します。
    /// <see cref="Appearance"/> クラスは様々なアピアランス要素を保存するコンテナ クラスです。
    /// DDD で使用するアピアランス要素は以下のとおりです。
    /// これらのアピアランス要素を変更する事で物体は単一のべた塗りから複雑な陰影までを表現する事が可能です。
    /// <list type="bullet">
    ///   <item>シェーダー：<see cref="ShaderProgram"/></item>
    ///   <item><c>Uniform</c> 変数アレイ：<see cref="UniformArray"/></item>
    ///   <item>マテリアル：<see cref="Material"/></item>
    ///   <item>テクスチャー：<see cref="Sampler"/></item>
    ///   <item>ポリゴン モード：<see cref="PolygonMode"/></item>
    ///   <item>コンポジット モード：<see cref="CompositeMode"/></item>
    ///   <item>ポイントスプライト モード：<see cref="PointSpriteMode"/></item>
    /// </list>
    /// これらのアピアランス要素は必要に応じて <see cref="Appearance"/> オブジェクトにセットします。
    /// セットされないアピアランス要素は規定のデフォルト値が使用されます。
    /// </remarks>
    /// <seealso cref="ShaderProgram"/>
    /// <seealso cref="UniformArray"/>
    /// <seealso cref="Material"/>
    /// <seealso cref="Sampler"/>
    /// <seealso cref="PolygonMode"/>
    /// <seealso cref="CompositeMode"/>
    /// <seealso cref="PointSpriteMode"/>
    /// <seealso cref="Mesh"/>
    public class Appearance : Object {

        #region Field
        ShaderProgram shader;
        Material material;
        List<UniformArray> uniformArrays;
        List<Sampler> samplers;
        PolygonMode polygonMode;
        CompositeMode compositeMode;
        PointSpriteMode pointSpriteMode;
        #endregion

        #region Constructor
        /// <summary>
        /// <see cref="Appearance"/> オブジェクトを作成するコンストラクター
        /// </summary>
        /// <remarks>
        /// 規定の初期は、
        /// <list type="bullet">
        ///   <item><see cref="ShaderProgram"/> = <c>null</c></item></list>
        ///   <item><see cref="Material"/> = <c>null</c></item>
        ///   <item><see cref="UniformArrays"/> = なし</item>
        ///   <item><see cref="Samplers"/> = なし</item>
        ///   <item><see cref="PointSpriteMode"/> = なし</item>
        /// </remarks>
        /// です。それぞれ規定のデフォルト値を使って描画されます。
        public Appearance () {
            this.shader = null;
            this.material = null;
            this.uniformArrays = new List<UniformArray> ();
            this.samplers = new List<Sampler>();
            this.polygonMode = null;
            this.compositeMode = null;
            this.pointSpriteMode = null;
        }
        #endregion

        #region Property
        /// <summary>
        /// シェーダー プログラム
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用されるシェーダー プログラム。
        /// </remarks>
        public ShaderProgram ShaderProgram {
            get { return shader; }
        }

        /// <summary>
        /// マテリアル
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用される <see cref="Material"/>。
        /// </remarks>
        public Material Material {
            get { return material; }
        }

        /// <summary>
        /// <c>Uniform</c> 変数アレイの個数
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用される<see cref="UniformArray"/>の個数
        /// </remarks>
        public int UniformArrayCount {
            get { return uniformArrays.Count; }
        }

        /// <summary>
        /// すべての <c>Uniform</c> 変数アレイを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用されるすべての <see cref="UniformArray"/>を列挙する列挙子
        /// </remarks>
        public IEnumerable<UniformArray> UniformArrays {
            get { return uniformArrays.Where(x => x != null); }
        }

        /// <summary>
        /// ポリゴン モード
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用される<see cref="PolygonMode"/>
        /// </remarks>
        public PolygonMode PolygonMode {
            get { return polygonMode; }
        }

        /// <summary>
        /// このアピアランスで使用されるコンポジット モード
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用される<see cref="CompositeMode"/>
        /// </remarks>
        public CompositeMode CompositeMode {
            get { return compositeMode; }
        }

        /// <summary>
        /// このアピアランスで使用されるポイント スプライト モード
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用される<see cref="PointSpriteMode"/>。
        /// </remarks>
        public PointSpriteMode PointSpriteMode {
            get { return pointSpriteMode; }
        }

        /// <summary>
        /// このアピアランスに現在セットされているサンプラーの個数
        /// </summary>
        /// <remarks>
        /// このアピアランスに現在セットされている<see cref="Sampler"/> の個数
        /// </remarks>
        public int SamplerCount {
            get { return samplers.Count(); }
        }

        /// <summary>
        /// このアピアランスでセットされているサンプラーを列挙する列挙子
        /// </summary>
        /// <summary>
        /// このアピアランスでセットされている <see cref="Sampler"/> を列挙する列挙子
        /// </summary>
        public IEnumerable<Sampler> Samplers {
            get { return samplers.Where (x => x != null); }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.Add (shader);
                objs.Add (material);
                objs.Add (polygonMode);
                objs.Add (compositeMode);
                objs.Add (pointSpriteMode);
                objs.AddRange (samplers);
                objs.AddRange (uniformArrays);
                objs.RemoveAll (x=>x==null);
                return objs;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// シェーダー プログラムのセット
        /// </summary>
        /// <remarks>
        /// この物体を描画するのに使用する<see cref="ShaderProgram"/>をセットします。
        /// <c>null</c>を指定するとセットされていたシェーダーが取り除かれます。
        /// </remarks>
        /// <param name="shader">シェーダー プログラム</param>
        /// <seealso cref="ShaderProgram"/>
        public void SetShaderProgram (ShaderProgram shader) {
            this.shader = shader;
        }

        /// <summary>
        /// マテリアルのセット
        /// </summary>
        /// <remarks>
        /// この物体を描画するのに使用する<see cref="Material"> をセットします。
        /// マテリアルはシェーダーで使用する予定の <c>Uniform</c> 変数を保存するプレイスホルダーとして機能します。
        /// オブジェクトのプロパティをシェーダーにエクスポートする方法については、<see cref="UniformArray"/> の解説を参照してください。
        /// <c>null</c> を指定するとセットされていたマテリアルが取り除かれます。
        /// <note type="caution">
        /// ここでマテリアルをセットしただけではシェーダーからアクセスできません。
        /// 必ず <see cref="AddUniformArray(UniformArray)"/> で適切に設定された <see cref="UniformArray"/> をセットしてください。
        /// </note>
        /// </remarks>
        /// <param name="material">マテリアル</param>
        /// <seealso cref="Material"/>
        /// <seealso cref="UniformArray"/>
        public void SetMaterial (Material material) {
            this.material = material;
        }

        /// <summary>
        /// <c>Uniform</c> 変数アレイのセット
        /// </summary>
        /// <remarks>
        /// セットされた <c>Uniform</c> 変数アレイに登録されているプロパティ値が
        /// シェーダーに <c>Uniform</c> 変数としてエクスポートされ、シェーダーの中で使用できるようになります。
        /// <c>Uniform</c> 変数は、この<see cref="UniformArray"/> 単位で追加・削除します。
        /// <see cref="UniformArray"/>は複数 <see cref="Appearance"/> オブジェクトに追加することが可能です。
        /// 既にセットされていた<see cref="UniformArray"/>オブジェクトを再度セットした場合何もしません。
        /// </remarks>
        /// <param name="uniformArray"><c>Uniform</c> 変数アレイ</param>
        /// <exception cref="ArgumentNullException"><paramref name="uniformArray"/></exception>
        /// <seealso cref="UniformArray"/>
        public void AddUniformArray (UniformArray uniformArray) {
            if (uniformArray == null) {
                throw new ArgumentNullException ("UniformArray is null");
            }
            if (uniformArrays.Contains (uniformArray)) {
                return;
            }

            this.uniformArrays.Add (uniformArray);
        }

        /// <summary>
        /// <c>Uniform</c> 変数アレイを削除します
        /// </summary>
        /// <remarks>
        /// 登録済みの <see cref="UniformArray"/> を削除します。
        /// <c>null</c>もしくはそれが登録されていなかった場合は何もしません。
        /// </remarks>
        /// <param name="uniformArray"><c>Uniform</c> 変数アレイ</param>
        /// <returns>削除したら<c>true</c>, そうでなければ <c>false</c></returns>
        /// <seealso cref="UniformArray"/>
        public bool RemoveUniformArray (UniformArray uniformArray) {
            return this.uniformArrays.Remove (uniformArray);
        }

        /// <summary>
        /// <c>Uniform</c> 変数アレイを取得します
        /// </summary>
        /// <remarks>
        /// 登録済みの <see cref="UniformArray"/> オブジェクトを取得します。
        /// インデックスは0から <c><see cref="UniformArrayCount"/>-1</c> までの整数値です。
        /// </remarks>
        /// <param name="index">インデックス番号</param>
        /// <returns><see cref="UniformArray"/>オブジェクト</returns>
        public UniformArray GetUniformArray (int index) {
            if (index < 0 || index >= UniformArrayCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return uniformArrays[index];
        }

        /// <summary>
        /// ポリゴン モードをセットします
        /// </summary>
        /// <remarks>
        /// <see cref="PolygonMode"/>をセットします。
        /// <see cref="PolygonMode"/>はカリングルールと面の向きを規定します。
        /// <c>null</c>を指定すると設定済みのものを削除します。
        /// </remarks>
        /// <param name="pmode"><see cref="PolygonMode"/>オブジェクト</param>
        /// <seealso cref="PolygonMode"/>
        public void SetPolygonMode (PolygonMode pmode) {
            this.polygonMode = pmode;
        }

        /// <summary>
        /// コンポジット モードをセットします
        /// </summary>
        /// <remarks>
        /// <see cref="CompositeMode"/>をセットします。
        /// <see cref="CompositeMode"/>はフラグメントをフレームバッファーに合成する方法を規定します。
        /// <c>null</c>を指定するとデフォルトの合成方法になります。
        /// </remarks>
        /// <param name="cmode"><see cref="CompositeMode"/> オブジェクト</param>
        /// <seealso cref="CompositeMode"/>
        public void SetComositeMode (CompositeMode cmode) {
            this.compositeMode = cmode;
        }

        /// <summary>
        /// ポイント スプライト モードをセットします
        /// </summary>
        /// <remarks>
        /// <see cref="PointSpriteMode"/> はポイントスプライトの描画方法を規定します。
        /// <c>null</c>を指定すると設定済みのものを削除します。
        /// このアピアランス要素はポイントスプライトを描画するときのみ使用されます。
        /// その他のプリミティブを描画するときは無視されます。
        /// </remarks>
        /// <param name="psmode">セットする<see cref="PointSpriteMode"/>オブジェクト</param>
        public void SetPointSpriteMode (PointSpriteMode psmode) {
            this.pointSpriteMode = psmode;
        }

        /// <summary>
        /// サンプラーをセットします
        /// </summary>
        /// <remarks>
        /// このアピアランスで使用する <see cref="Sampler"/see> 登録します。
        /// すでに登録されているサンプラーは無視します｡
        /// </remarks>
        /// <param name="sampler">サンプラー</param>
        public void AddSampler (Sampler sampler) {
            if (sampler == null) {
                throw new ArgumentNullException ("Sampler is null");
            }
            if (samplers.Contains (sampler)) {
                return;
            }

            this.samplers.Add (sampler);
        }

        /// <summary>
        /// サンプラーを削除します
        /// </summary>
        /// <remarks>
        /// このアピアランスから指定のサンプラーを削除します。
        /// サンプラーがnullまたは登録されていない場合は安全に無視します。
        /// </remarks>
        /// <param name="sampler">サンプラー</param>
        public bool RemoveSampler (Sampler sampler) {
            return samplers.Remove (sampler);
        }

        /// <summary>
        /// サンプラーを取得します
        /// </summary>
        /// <remarks>
        /// 指定の番号のサンプラーを取得します。
        /// </remarks>
        /// <param name="index">サンプラー番号</param>
        /// <returns>サンプラーオブジェクト</returns>
        public Sampler GetSampler (int index) {
            if (index < 0 || index >= SamplerCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return samplers[index];
        }
        
        /// <summary>
        /// サンプラー番号を取得します
        /// </summary>
        /// <remarks>
        /// 指定のサンプラーのサンプラー番号を取得します。
        /// GLSLではテクスチャーを読み込む為にはこのサンプラー番号をシェーダーにセットします｡
        /// </remarks>
        /// <param name="sampler">サンプラー</param>
        /// <returns>サンプラー番号</returns>
        public int GetSamplerIndex (Sampler sampler) {
            if (sampler == null) {
                throw new ArgumentNullException ("Sampler is null");
            }
            if (!samplers.Contains (sampler)) {
                throw new ArgumentException ("Sampler was not found");
            }

            return samplers.IndexOf (sampler);
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("Appearance: SP={0}", ShaderProgram);
        }

        #endregion


    }
}
